﻿using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using Microsoft.SharePoint.Linq;
using ZhukBlogLinqExamples.Model;
using ZhukBlogLinqExamples.Repositories;

namespace ZhukBlogLinqExamples
{
    ///<summary>
    ///</summary>
    public class SPLinqContentIterator<TEntity>
        where TEntity : ZhukDataItem, new()
    {
        private readonly BaseRepository<TEntity, DataContext> _repository;
        private readonly Expression<Func<TEntity, bool>> _expression;
        private string _path;
        private bool _recursive;
        private EntityProcessor _entityProcessor;
        private EntityProcessorErrorCallout _entityProcessorErrorCallout;

        public event AsyncCompletedEventHandler IteratorCompleted;
        public delegate void EntityProcessor(TEntity entity, out bool isCancelled);
        public delegate void EntityProcessorErrorCallout(TEntity entity, Exception exception, out bool isCancelled);
        private delegate void ContentIteratorHandler(BaseRepository<TEntity, DataContext> repository,
            Expression<Func<TEntity, bool>> expression,
            string path, bool recursive,
            int leftId, int rightId,
            EntityProcessor entityProcessor,
            EntityProcessorErrorCallout entityProcessorErrorCallout, out bool cancelled);

        /// <summary>
        /// Инициализация итератора
        /// </summary>
        /// <param name="repository">Репозиторий</param>
        /// <param name="expression">Предикат для фильтрации данных</param>
        /// <param name="path">Путь к папке</param>
        /// <param name="recursive">Просматривать дочерние папки</param>
        /// <param name="entityProcessor">Делегат для обработки элемента</param>
        /// <param name="entityProcessorErrorCallout">Делегат для обработки исключения</param>
        public SPLinqContentIterator(BaseRepository<TEntity, DataContext> repository,
            Expression<Func<TEntity, bool>> expression,
            string path, bool recursive,
            EntityProcessor entityProcessor,
            EntityProcessorErrorCallout entityProcessorErrorCallout)
        {
            _repository = repository;
            _expression = expression;
            _path = path;
            _recursive = recursive;
            _entityProcessor = entityProcessor;
            _entityProcessorErrorCallout = entityProcessorErrorCallout;
        }

        private int? MaxId
        {
            get
            {
                return _repository.CurrentList
                    .ScopeToFolder(_path, _recursive)
                    .Where(_expression)
                    .OrderByDescending(x => x.Id)
                    .Select(x => x.Id)
                    .Take(1)
                    .FirstOrDefault();
            }
        }

        private int? MinId
        {
            get
            {
                return _repository.CurrentList
                    .ScopeToFolder(_path, _recursive)
                    .Where(_expression)
                    .OrderBy(x => x.Id)
                    .Select(x => x.Id)
                    .Take(1)
                    .FirstOrDefault();
            }
        }

        private int MaxItemsPerBatch
        {
            get
            {
                return Convert.ToInt32(_repository.MetaData.List.ParentWeb
                    .Site.WebApplication.MaxItemsPerThrottledOperation);
            }
        }

        public void Begin()
        {
            if (!MaxId.HasValue || !MinId.HasValue) return;
            var cancelled = false;
            for (var i = MinId.Value; i < MaxId.Value; i = i + MaxItemsPerBatch)
            {
                var leftId = i;
                var rightId = i + MaxItemsPerBatch - 1;

                var evenWork = new ContentIteratorHandler(ContentIteratorWorker);
                evenWork.Invoke(_repository, _expression, _path, _recursive, leftId, rightId, _entityProcessor,
                                     _entityProcessorErrorCallout, out cancelled);
                if (cancelled) return;
            }
            return;
        }

        private static void ContentIteratorWorker(
            BaseRepository<TEntity, DataContext> repository,
            Expression<Func<TEntity, bool>> expression,
            string path, bool recursive,
            int leftId, int rightId,
            EntityProcessor entityProcessor,
            EntityProcessorErrorCallout entityProcessorErrorCallout,
            out bool cancelled)
        {

            // Флаг отмены итерации опущен по умолчанию
            cancelled = false;
            // Получаем элементы, подлежащие обработке
            var items = repository.GetEntityCollection(expression, path, recursive)
                // Накладываем ограничение по полю ID
                .Where(x => x.Id >= leftId && x.Id <= rightId)
                .ToList();
            Console.WriteLine("Worker: " + items.Count);
            foreach (var item in items)
            {
                // Если обработчик указан
                if (entityProcessor != null)
                {
                    try
                    {
                        // Исполняем
                        entityProcessor(item, out cancelled);
                        // Сохраняем элемент
                        repository.SaveEntity(item);
                    }
                    catch (Exception ex)
                    {
                        // Если указан обработчик исключения, то исполняем его
                        if (entityProcessorErrorCallout != null)
                            entityProcessorErrorCallout(item, ex, out cancelled);
                    }
                }
                if (cancelled)
                {
                    // Если флаг отмены поднят, то возвращаем управление
                    return;
                }
            }
        }
    }
}
